import {
  Component,
  Input,
  ChangeDetectionStrategy,
  ViewEncapsulation,
  AfterViewInit,
  ElementRef,
  ViewChild,
  ChangeDetectorRef,
  OnInit,
  OnDestroy,
  OnChanges,
  SimpleChanges,
} from '@angular/core';
import { BehaviorSubject, debounce, Observable, ReplaySubject, Subject, timer } from 'rxjs';
import { distinctUntilChanged, startWith, switchMap, takeUntil } from 'rxjs/operators';
import { LvConfig } from '../core/config';
import { I18NService } from '../core/i18n/i18n.service';
import { InputBoolean, InputNumber } from '../core/utils/convert';
import { TypeUtils } from '../core/utils/type';
import { LvLoadingSize } from '../core/types';

@Component({
  selector: 'lv-loading',
  exportAs: 'lvLoading',
  templateUrl: './loading.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class.lv-loading-no-children]': '!hasChildren',
  },
})
export class LoadingComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges {
  @Input() @InputNumber() lvDelay = 0;

  @Input() @InputBoolean() lvMask = true;

  @Input() @InputBoolean() lvLoading = false;

  @Input() lvSize: LvLoadingSize | number = 'default';

  @Input() lvLoadingText = this.i18n.get('loadingText');

  @ViewChild('loadingContainer') loadingContainer: ElementRef;
  @ViewChild('loadingRef', { static: true }) loadingRef: ElementRef;
  loading = false;

  typeUtils = TypeUtils;
  assetsPrefixPath = LvConfig.assetsPrefixPath;
  private hasChildren = false;
  private destroy$ = new Subject<void>();

  private delay$ = new ReplaySubject<number>(1);
  private loading$ = new BehaviorSubject(this.lvLoading);

  constructor(
    private i18n: I18NService,
    private cdr: ChangeDetectorRef,
  ) {}

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }

  ngOnChanges(changes: SimpleChanges): void {
    const { lvLoading, lvDelay } = changes;

    if (lvLoading) {
      this.loading$.next(this.lvLoading);
    }

    if (lvDelay) {
      this.delay$.next(this.lvDelay);
    }
  }

  ngAfterViewInit(): void {
    // 适配动态加载 loading 组件
    setTimeout((): void => {
      if (this.loadingContainer?.nativeElement) {
        this.hasChildren = Boolean(this.loadingContainer.nativeElement.nextElementSibling);
      } else {
        this.hasChildren = Boolean(this.loadingRef.nativeElement?.children?.length);
      }

      this.cdr.markForCheck();
    });
  }

  ngOnInit(): void {
    this.delay$
      .pipe(
        startWith(this.lvDelay),
        distinctUntilChanged(),
        switchMap((delay): Observable<boolean> | BehaviorSubject<boolean> => {
          if (delay === 0) {
            return this.loading$;
          }

          return this.loading$.pipe(debounce((loading): Observable<0> => timer(loading ? delay : 0)));
        }),
        takeUntil(this.destroy$),
      )
      .subscribe((loading: boolean): void => {
        this.loading = loading;
        this.cdr.markForCheck();
      });
  }
}
